import requests
import json
import os
import time
from datetime import datetime
from typing import Optional

class WeChatTokenManager:
    """
    微信公众号 Access Token 获取工具类
    """
    
    def __init__(self, app_id: str, app_secret: str):
        """
        初始化
        :param app_id: 微信公众号AppID
        :param app_secret: 微信公众号AppSecret
        """
        self.app_id = app_id
        self.app_secret = app_secret
        self.token_file = os.path.join(os.path.dirname(__file__), 'access_token.json')
        self.token: Optional[str] = None
        self.expires_time: int = 0
    
    def get_access_token(self) -> str:
        """
        获取 Access Token
        :return: Access Token
        """
        try:
            # 先尝试从本地文件读取token
            if self._load_token_from_file():
                return self.token
            
            # 如果本地没有或已过期，从微信服务器获取新token
            return self._fetch_new_token()
        except Exception as e:
            print(f"获取Access Token失败: {e}")
            raise e
    
    def _load_token_from_file(self) -> bool:
        """
        从本地文件加载token
        :return: 是否成功加载有效token
        """
        try:
            if not os.path.exists(self.token_file):
                return False
            
            with open(self.token_file, 'r', encoding='utf-8') as f:
                token_data = json.load(f)
            
            # 检查token是否过期（提前5分钟刷新）
            current_time = int(time.time() * 1000)
            if current_time < token_data['expires_time'] - 300000:
                self.token = token_data['access_token']
                self.expires_time = token_data['expires_time']
                print("从本地文件加载Access Token成功")
                return True
            
            return False
        except Exception as e:
            print(f"读取本地token文件失败: {e}")
            return False
    
    def _fetch_new_token(self) -> str:
        """
        从微信服务器获取新的Access Token
        :return: Access Token
        """
        url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={self.app_id}&secret={self.app_secret}"
        
        print("正在从微信服务器获取新的Access Token...")
        
        try:
            response = requests.get(url, timeout=30)
            response.raise_for_status()
            
            result = response.json()
            
            if 'errcode' in result:
                raise Exception(f"微信API错误: {result['errcode']} - {result['errmsg']}")
            
            self.token = result['access_token']
            self.expires_time = int(time.time() * 1000) + (result['expires_in'] * 1000)
            
            # 保存token到本地文件
            self._save_token_to_file()
            
            print("获取Access Token成功")
            return self.token
            
        except requests.RequestException as e:
            raise Exception(f"请求微信API失败: {e}")
        except json.JSONDecodeError as e:
            raise Exception(f"解析微信API响应失败: {e}")
    
    def _save_token_to_file(self):
        """
        保存token到本地文件
        """
        try:
            token_data = {
                'access_token': self.token,
                'expires_time': self.expires_time,
                'update_time': datetime.now().isoformat()
            }
            
            with open(self.token_file, 'w', encoding='utf-8') as f:
                json.dump(token_data, f, ensure_ascii=False, indent=2)
            
            print("Access Token已保存到本地文件")
        except Exception as e:
            print(f"保存token到文件失败: {e}")
    
    def refresh_token(self) -> str:
        """
        强制刷新token
        :return: 新的Access Token
        """
        print("强制刷新Access Token...")
        return self._fetch_new_token()
    
    def is_token_valid(self) -> bool:
        """
        检查token是否有效
        :return: token是否有效
        """
        current_time = int(time.time() * 1000)
        return self.token and current_time < self.expires_time - 300000
    
    def get_token_remaining_time(self) -> int:
        """
        获取token剩余有效时间（秒）
        :return: 剩余秒数
        """
        if not self.token:
            return 0
        current_time = int(time.time() * 1000)
        return max(0, (self.expires_time - current_time) // 1000)
    
    def get_token_info(self) -> dict:
        """
        获取token详细信息
        :return: token信息字典
        """
        return {
            'access_token': self.token,
            'is_valid': self.is_token_valid(),
            'remaining_time': self.get_token_remaining_time(),
            'expires_time': datetime.fromtimestamp(self.expires_time / 1000).isoformat() if self.expires_time else None
        }


class WeChatAPI:
    """
    微信公众号API调用封装类
    """
    
    def __init__(self, token_manager: WeChatTokenManager):
        """
        初始化
        :param token_manager: Token管理器实例
        """
        self.token_manager = token_manager
    
    def call_api(self, api_path: str, method: str = 'GET', data: dict = None) -> dict:
        """
        调用微信API
        :param api_path: API路径
        :param method: 请求方法
        :param data: 请求数据
        :return: API响应
        """
        token = self.token_manager.get_access_token()
        url = f"https://api.weixin.qq.com{api_path}"
        
        # 添加access_token参数
        if '?' in url:
            url += f"&access_token={token}"
        else:
            url += f"?access_token={token}"
        
        try:
            if method.upper() == 'POST':
                response = requests.post(url, json=data, timeout=30)
            else:
                response = requests.get(url, timeout=30)
            
            response.raise_for_status()
            result = response.json()
            
            # 检查是否token过期
            if result.get('errcode') == 40001:
                print("Token已过期，正在刷新...")
                token = self.token_manager.refresh_token()
                # 重新构建URL
                url = url.replace(f"access_token={self.token_manager.token}", f"access_token={token}")
                
                # 重新发起请求
                if method.upper() == 'POST':
                    response = requests.post(url, json=data, timeout=30)
                else:
                    response = requests.get(url, timeout=30)
                
                response.raise_for_status()
                result = response.json()
            
            return result
            
        except requests.RequestException as e:
            raise Exception(f"API请求失败: {e}")
        except json.JSONDecodeError as e:
            raise Exception(f"解析API响应失败: {e}")


def example():
    """
    使用示例
    """
    # 请替换为您的实际AppID和AppSecret
    APP_ID = 'wxf7752d1f6e6348de'
    APP_SECRET = '59799b2703d5cdb955c86556ddda1254'
    
    # 创建token管理器
    token_manager = WeChatTokenManager(APP_ID, APP_SECRET)
    
    try:
        # 获取Access Token
        token = token_manager.get_access_token()
        print(f"当前Access Token: {token}")
        print(f"Token剩余有效时间: {token_manager.get_token_remaining_time()} 秒")
        
        # 获取token详细信息
        token_info = token_manager.get_token_info()
        print(f"Token详细信息: {json.dumps(token_info, ensure_ascii=False, indent=2)}")
        
        # 创建API调用器（示例）
        api = WeChatAPI(token_manager)
        
        # 示例：获取微信服务器IP列表
        # result = api.call_api('/cgi-bin/getcallbackip')
        # print(f"微信服务器IP列表: {result}")
        
    except Exception as e:
        print(f"示例运行失败: {e}")


if __name__ == '__main__':
    example()
